package ar.com.e2solver.solver.operator.crossover.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.model.Coordinate;
import ar.com.e2solver.model.Piece;
import ar.com.e2solver.model.Puzzle;
import ar.com.e2solver.solver.Individual;
import ar.com.e2solver.solver.RandomConstants;
import ar.com.e2solver.solver.operator.OperatorUtil;
import ar.com.e2solver.solver.operator.crossover.CrossoverOperator;

/**
 * Intercambia regiones entre rompecabezas. La solucion final es un
 * rompecabezas valido.
 * @author rodo
 *
 */
public final class RegionExchangeCrossoverOperator implements CrossoverOperator {
	
	private static RegionExchangeCrossoverOperator instance = new RegionExchangeCrossoverOperator();
	private static final int PROPORTION = 2;
	
	private RegionExchangeCrossoverOperator(){}
	
	public static RegionExchangeCrossoverOperator getInstance(){
		return instance;
	}

	public List<Individual> operate(Individual individual1, Individual individual2) throws BusinessException {
		Puzzle p1 = individual1.getPuzzle();
		Puzzle p2 = individual2.getPuzzle();
		Puzzle result = p1.clone();
		
		List<Coordinate> regionToExchange = getRegionToExchange(p2.getRowsNumber(), p2.getColumnNumbers());
		
		Piece piece;
		Coordinate coordinate, toSwap;
		for (Iterator<Coordinate> iterator = regionToExchange.iterator(); iterator.hasNext();) {
			coordinate = iterator.next();
			toSwap = result.findCoordinateForPiece(p2.getPieceAt(coordinate));
			
			if(result.canSwap(coordinate, toSwap)){
				result.swapPositions(coordinate, toSwap);
				
				piece = result.getPieceAt(coordinate);
				
				if(piece.isABorderPiece()){
					OperatorUtil.rotateForBorder(coordinate, result);
					OperatorUtil.rotateForBorder(toSwap, result);
				} else if(piece.isACornerPiece()) {
					OperatorUtil.rotateForCorner(coordinate, result);
					OperatorUtil.rotateForCorner(toSwap, result);
				} else {
					rotateToSamePosition(piece, p2.getPieceAt(coordinate));
				}
			}
		}
		
		List<Individual> results = new ArrayList<Individual>();
		results.add(new Individual(result));
		return results;
	}


	private List<Coordinate> getRegionToExchange(int rows, int cols) {
		List<Coordinate> region = new ArrayList<Coordinate>();
		int regionSizeX = rows / PROPORTION;
		int regionSizeY = cols / PROPORTION;
		
		int startX = RandomConstants.RANDOM.nextInt(rows - regionSizeX + 1);
		int startY = RandomConstants.RANDOM.nextInt(cols - regionSizeY + 1);
		int endX = startX + regionSizeX - 1;
		int endY = startY + regionSizeY - 1;
		
		for(int i = startX; i <= endX; i++){
			for(int j = startY; j <= endY; j++){
				region.add(new Coordinate(i, j));
			}
		}

		return region;
	}

	
	private void rotateToSamePosition(Piece pieceToRotate, Piece pieceToCopy) {
		boolean areInSamePosition = pieceToRotate.getTop().equals(pieceToCopy.getTop()) && pieceToRotate.getRight().equals(pieceToCopy.getRight())
				&& pieceToRotate.getBottom().equals(pieceToCopy.getBottom()) && pieceToRotate.getLeft().equals(pieceToCopy.getLeft());
		
		while(!areInSamePosition){
			pieceToRotate.rotateToRight(1);
			areInSamePosition = pieceToRotate.getTop().equals(pieceToCopy.getTop()) && pieceToRotate.getRight().equals(pieceToCopy.getRight())
					&& pieceToRotate.getBottom().equals(pieceToCopy.getBottom()) && pieceToRotate.getLeft().equals(pieceToCopy.getLeft());
		}
	}

}
